/*
 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.activation;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.io.*;
import java.util.Date;
import java.util.Properties;

import org.omg.CORBA.ORB;
import com.sun.corba.se.spi.activation.Activator;
import com.sun.corba.se.spi.activation.ActivatorHelper;
import com.sun.corba.se.impl.orbutil.ORBConstants;

/**
 * @author Ken Cavanaugh
 * @since JDK1.2
 */
public class ServerMain {
    /* TODO:
    * 1.  Rewrite all uses of ORB properties to use constants from someplace.
    *     The strings are scattered between here, the ORB classes, and
    *     ServerTableEntry.
    * 2.  Consider a more general log facility.
    * 3.  Remove ServerCallback from POAORB.
    * 4.  Needs to be merged with Harold's changes to support SSL.
    * 5.  Logs need to be internationalized.
    */

  public final static int OK = 0;
  public final static int MAIN_CLASS_NOT_FOUND = 1;
  public final static int NO_MAIN_METHOD = 2;
  public final static int APPLICATION_ERROR = 3;
  public final static int UNKNOWN_ERROR = 4;
  public final static int NO_SERVER_ID = 5;
  public final static int REGISTRATION_FAILED = 6;

  public static String printResult(int result) {
    switch (result) {
      case OK:
        return "Server terminated normally";
      case MAIN_CLASS_NOT_FOUND:
        return "main class not found";
      case NO_MAIN_METHOD:
        return "no main method";
      case APPLICATION_ERROR:
        return "application error";
      case NO_SERVER_ID:
        return "server ID not defined";
      case REGISTRATION_FAILED:
        return "server registration failed";
      default:
        return "unknown error";
    }
  }

  private void redirectIOStreams() {
    // redirect out and err streams
    try {
      String logDirName =
          System.getProperty(ORBConstants.DB_DIR_PROPERTY) +
              System.getProperty("file.separator") +
              ORBConstants.SERVER_LOG_DIR +
              System.getProperty("file.separator");

      File logDir = new File(logDirName);
      String server = System.getProperty(
          ORBConstants.SERVER_ID_PROPERTY);

      FileOutputStream foutStream =
          new FileOutputStream(logDirName + server + ".out", true);
      FileOutputStream ferrStream =
          new FileOutputStream(logDirName + server + ".err", true);

      PrintStream pSout = new PrintStream(foutStream, true);
      PrintStream pSerr = new PrintStream(ferrStream, true);

      System.setOut(pSout);
      System.setErr(pSerr);

      logInformation("Server started");

    } catch (Exception ex) {
    }
  }

  /**
   * Write a time-stamped message to the indicated PrintStream.
   */
  private static void writeLogMessage(PrintStream pstream, String msg) {
    Date date = new Date();
    pstream.print("[" + date.toString() + "] " + msg + "\n");
  }

  /**
   * Write information to standard out only.
   */
  public static void logInformation(String msg) {
    writeLogMessage(System.out, "        " + msg);
  }

  /**
   * Write error message to standard out and standard err.
   */
  public static void logError(String msg) {
    writeLogMessage(System.out, "ERROR:  " + msg);
    writeLogMessage(System.err, "ERROR:  " + msg);
  }

  /**
   * Write final message to log(s) and then terminate by calling
   * System.exit( code ).  If code == OK, write a normal termination
   * message to standard out, otherwise write an abnormal termination
   * message to standard out and standard error.
   */
  public static void logTerminal(String msg, int code) {
    if (code == 0) {
      writeLogMessage(System.out, "        " + msg);
    } else {
      writeLogMessage(System.out, "FATAL:  " +
          printResult(code) + ": " + msg);

      writeLogMessage(System.err, "FATAL:  " +
          printResult(code) + ": " + msg);
    }

    System.exit(code);
  }

  private Method getMainMethod(Class serverClass) {
    Class argTypes[] = new Class[]{String[].class};
    Method method = null;

    try {
      method = serverClass.getDeclaredMethod("main", argTypes);
    } catch (Exception exc) {
      logTerminal(exc.getMessage(), NO_MAIN_METHOD);
    }

    if (!isPublicStaticVoid(method)) {
      logTerminal("", NO_MAIN_METHOD);
    }

    return method;
  }

  private boolean isPublicStaticVoid(Method method) {
    // check modifiers: public static
    int modifiers = method.getModifiers();
    if (!Modifier.isPublic(modifiers) || !Modifier.isStatic(modifiers)) {
      logError(method.getName() + " is not public static");
      return false;
    }

    // check return type and exceptions
    if (method.getExceptionTypes().length != 0) {
      logError(method.getName() + " declares exceptions");
      return false;
    }

    if (!method.getReturnType().equals(Void.TYPE)) {
      logError(method.getName() + " does not have a void return type");
      return false;
    }

    return true;
  }

  private Method getNamedMethod(Class serverClass, String methodName) {
    Class argTypes[] = new Class[]{org.omg.CORBA.ORB.class};
    Method method = null;

    try {
      method = serverClass.getDeclaredMethod(methodName, argTypes);
    } catch (Exception exc) {
      return null;
    }

    if (!isPublicStaticVoid(method)) {
      return null;
    }

    return method;
  }

  private void run(String[] args) {
    try {
      redirectIOStreams();

      String serverClassName = System.getProperty(
          ORBConstants.SERVER_NAME_PROPERTY);

      // determine the class loader to be used for loading the class
      // since ServerMain is going to be in JDK and we need to have this
      // class to load application classes, this is required here.
      ClassLoader cl = Thread.currentThread().getContextClassLoader();

      if (cl == null) {
        cl = ClassLoader.getSystemClassLoader();
      }

      // determine the main class
      Class serverClass = null;

      try {
        // determine the main class, try loading with current class loader
        serverClass = Class.forName(serverClassName);
      } catch (ClassNotFoundException ex) {
        // eat the exception and try to load using SystemClassLoader
        serverClass = Class.forName(serverClassName, true, cl);
      }

      if (debug) {
        System.out.println("class " + serverClassName + " found");
      }

      // get the main method
      Method mainMethod = getMainMethod(serverClass);

      // This piece of code is required, to verify the server definition
      // without launching it.

      // verify the server

      boolean serverVerifyFlag = Boolean.getBoolean(
          ORBConstants.SERVER_DEF_VERIFY_PROPERTY);
      if (serverVerifyFlag) {
        if (mainMethod == null) {
          logTerminal("", NO_MAIN_METHOD);
        } else {
          if (debug) {
            System.out.println("Valid Server");
          }
          logTerminal("", OK);
        }
      }

      registerCallback(serverClass);

      // build args to the main and call it
      Object params[] = new Object[1];
      params[0] = args;
      mainMethod.invoke(null, params);

    } catch (ClassNotFoundException e) {
      logTerminal("ClassNotFound exception: " + e.getMessage(),
          MAIN_CLASS_NOT_FOUND);
    } catch (Exception e) {
      logTerminal("Exception: " + e.getMessage(),
          APPLICATION_ERROR);
    }
  }

  public static void main(String[] args) {
    ServerMain server = new ServerMain();
    server.run(args);
  }

  private static final boolean debug = false;

  private int getServerId() {
    Integer serverId = Integer.getInteger(ORBConstants.SERVER_ID_PROPERTY);

    if (serverId == null) {
      logTerminal("", NO_SERVER_ID);
    }

    return serverId.intValue();
  }

  private void registerCallback(Class serverClass) {
    Method installMethod = getNamedMethod(serverClass, "install");
    Method uninstallMethod = getNamedMethod(serverClass, "uninstall");
    Method shutdownMethod = getNamedMethod(serverClass, "shutdown");

    Properties props = new Properties();
    props.put("org.omg.CORBA.ORBClass",
        "com.sun.corba.se.impl.orb.ORBImpl");
    // NOTE: Very important to pass this property, otherwise the
    // Persistent Server registration will be unsucessfull.
    props.put(ORBConstants.ACTIVATED_PROPERTY, "false");
    String args[] = null;
    ORB orb = ORB.init(args, props);

    ServerCallback serverObj = new ServerCallback(orb,
        installMethod, uninstallMethod, shutdownMethod);

    int serverId = getServerId();

    try {
      Activator activator = ActivatorHelper.narrow(
          orb.resolve_initial_references(ORBConstants.SERVER_ACTIVATOR_NAME));
      activator.active(serverId, serverObj);
    } catch (Exception ex) {
      logTerminal("exception " + ex.getMessage(),
          REGISTRATION_FAILED);
    }
  }
}

class ServerCallback extends
    com.sun.corba.se.spi.activation._ServerImplBase {

  private ORB orb;
  private transient Method installMethod;
  private transient Method uninstallMethod;
  private transient Method shutdownMethod;
  private Object methodArgs[];

  ServerCallback(ORB orb, Method installMethod, Method uninstallMethod,
      Method shutdownMethod) {
    this.orb = orb;
    this.installMethod = installMethod;
    this.uninstallMethod = uninstallMethod;
    this.shutdownMethod = shutdownMethod;

    orb.connect(this);

    methodArgs = new Object[]{orb};
  }

  private void invokeMethod(Method method) {
    if (method != null) {
      try {
        method.invoke(null, methodArgs);
      } catch (Exception exc) {
        ServerMain.logError("could not invoke " + method.getName() +
            " method: " + exc.getMessage());
      }
    }
  }

  // shutdown the ORB and wait for completion
  public void shutdown() {
    ServerMain.logInformation("Shutdown starting");

    invokeMethod(shutdownMethod);

    orb.shutdown(true);

    ServerMain.logTerminal("Shutdown completed", ServerMain.OK);
  }

  public void install() {
    ServerMain.logInformation("Install starting");

    invokeMethod(installMethod);

    ServerMain.logInformation("Install completed");
  }

  public void uninstall() {
    ServerMain.logInformation("uninstall starting");

    invokeMethod(uninstallMethod);

    ServerMain.logInformation("uninstall completed");
  }
}
